﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace TinyAsset
{
    public class CacheRegistry<T> where T : BaseLoader<T>
    {
        LinkedList<T> Pending = new LinkedList<T>();
        LinkedList<T> Running = new LinkedList<T>();
        LinkedList<T> Completed = new LinkedList<T>();
        LinkedList<T> Unloading = new LinkedList<T>();

        Dictionary<string, T> Cached = new Dictionary<string, T>();

        float lastUpdateTime;
        readonly int MAX_RUNNING_QUEUE_LENGTH = 20;
        bool Busy { get { return Time.realtimeSinceStartup - lastUpdateTime > 0.01f; } }

        public void AddLast(T val)
        {
            if (val.IsRunning || val.IsCompleted && !val.IsDone)
                return;

            if (Running.Count < MAX_RUNNING_QUEUE_LENGTH)
                Running.AddLast(val);
            else
                Pending.AddLast(val);
        }

        public void MakeCache(T val)
        {
            Cached.Add(val.Key, val);
        }

        public bool TryGetValue(string key, out T val)
        {
            return Cached.TryGetValue(key, out val);
        }

        public void Unload(string key)
        {
            if (Cached.TryGetValue(key, out T loader))
            {
                loader.DecrementReferenceCount();
                if (!loader.IsAlive())
                    Unloading.AddLast(loader);
            }
        }

        public void Update(float deltaTime, float unscaledDeltaTime)
        {
            lastUpdateTime = Time.realtimeSinceStartup;

            LinkedListNode<T> first, next;

            while (Completed.First != null)
            {
                first = Completed.First;
                Completed.RemoveFirst();

                if (!first.Value.IsAlive())
                    Unloading.AddLast(first);

                first.Value.NotifyCompletion();

                if (Busy)
                    return;
            }

            first = Running.First;
            while (first != null)
            {
                next = first.Next;
                first.Value.Update(deltaTime, unscaledDeltaTime);
                if (first.Value.IsCompleted)
                {
                    Running.Remove(first);
                    Completed.AddLast(first);
                }
                first = next;
            }

            first = Unloading.First;
            while (first != null)
            {
                next = first.Next;
                if (first.Value.IsAlive())
                    Unloading.Remove(first);
                else
                {
                    if (first.Value.IsDone)
                    {
                        first.Value.Unload();
                        Unloading.Remove(first);
                        Cached.Remove(first.Value.Key);
                    }
                }
                first = next;
            }

            while (Pending.Count > 0 && Running.Count < MAX_RUNNING_QUEUE_LENGTH)
            {
                var item = Pending.First;
                Pending.RemoveFirst();
                Running.AddLast(item);
            }
        }
    }
}